/*
 * Copyright 2004-2014 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
 * versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 *
 * http://ec.europa.eu/idabc/eupl
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the Licence for the specific language governing permissions and limitations
 * under the Licence.
*/

package com.eviware.soapui.impl.wsdl.panels.teststeps.support;

import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.model.settings.Settings;
import com.eviware.soapui.model.settings.SettingsListener;
import com.eviware.soapui.model.support.ModelSupport;
import com.eviware.soapui.settings.UISettings;
import com.eviware.soapui.support.DocumentListenerAdapter;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.components.JEditorStatusBar.JEditorStatusBarTarget;
import com.eviware.soapui.support.scripting.groovy.GroovyScriptEngineFactory;
import com.eviware.soapui.support.scripting.js.JsScriptEngineFactory;
import com.eviware.soapui.support.xml.actions.EnableLineNumbersAction;
import com.eviware.soapui.support.xml.actions.FormatXmlAction;
import com.eviware.soapui.support.xml.actions.GoToLineAction;
import com.eviware.soapui.ui.support.FindAndReplaceDialogView;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.RTextScrollPane;

import javax.swing.*;
import javax.swing.event.CaretListener;
import javax.swing.text.Document;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import static com.eviware.soapui.impl.wsdl.teststeps.Script.SCRIPT_PROPERTY;

/**
 * Groovy editor wrapper
 *
 * @author ole.matzura
 */

public class GroovyEditor extends JPanel implements JEditorStatusBarTarget, PropertyChangeListener {
    private final RSyntaxTextArea editArea;
    private final GoToLineAction goToLineAction;
    private final EnableLineNumbersAction enableLineNumbersAction;
    private FindAndReplaceDialogView findAndReplaceDialog;
    private FormatXmlAction formatXmlAction;
    private GroovyEditorModel model;
    private final InternalSettingsListener settingsListener;
    private final GroovyDocumentListener groovyDocumentListener;
    private final RTextScrollPane scrollPane;
    private boolean updating;

    public GroovyEditor(GroovyEditorModel model) {
        super(new BorderLayout());
        this.model = model;

        model.addPropertyChangeListener(this);

        Settings settings = model.getSettings();
        Font editorFont = UISupport.getEditorFont(settings);

        editArea = new RSyntaxTextArea();
        editArea.restoreDefaultSyntaxScheme();

        String defaultScriptLanguage = ((WsdlProject) ModelSupport.getModelItemProject(model.getModelItem()))
                .getDefaultScriptLanguage();
        if (defaultScriptLanguage.equals(GroovyScriptEngineFactory.ID)) {
            editArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_GROOVY);
        } else if (defaultScriptLanguage.equals(JsScriptEngineFactory.ID)) {
            editArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT);
        }

        editArea.setFont(editorFont);
        editArea.setBorder(BorderFactory.createMatteBorder(0, 2, 0, 0, Color.WHITE));

        editArea.setText(model.getScript());
        editArea.setCaretPosition(0);
        editArea.setHighlightCurrentLine(false);
        Action runAction = model.getRunAction();
        if (runAction != null) {
            editArea.getInputMap().put(KeyStroke.getKeyStroke("alt ENTER"), "run-action");
            editArea.getActionMap().put("run-action", runAction);
        }

        groovyDocumentListener = new GroovyDocumentListener();
        editArea.getDocument().addDocumentListener(groovyDocumentListener);

        settingsListener = new InternalSettingsListener();
        settings.addSettingsListener(settingsListener);

        scrollPane = new RTextScrollPane(editArea, true);
        scrollPane.setPreferredSize(new Dimension(500, 300));
        add(scrollPane);

        UISupport.addPreviewCorner(scrollPane, true);

        addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent e) {
                editArea.requestFocusInWindow();
            }
        });

        JPopupMenu popup = editArea.getPopupMenu();
        findAndReplaceDialog = new FindAndReplaceDialogView(editArea);
        if (UISupport.isMac()) {
            editArea.getInputMap().put(KeyStroke.getKeyStroke("meta F"), findAndReplaceDialog);
        } else {
            editArea.getInputMap().put(KeyStroke.getKeyStroke("ctrl F"), findAndReplaceDialog);
        }
        popup.add(findAndReplaceDialog);
        popup.addSeparator();
        goToLineAction = new GoToLineAction(editArea, "Go To Line");
        enableLineNumbersAction = new EnableLineNumbersAction(scrollPane, "Show Line Numbers");

        popup.add(goToLineAction);
        popup.add(enableLineNumbersAction);

        editArea.getInputMap().put(KeyStroke.getKeyStroke("control G"), goToLineAction);
        editArea.getInputMap().put(KeyStroke.getKeyStroke("control L"), enableLineNumbersAction);
        editArea.setComponentPopupMenu(popup);
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);

        editArea.setEnabled(enabled);
    }

    public RSyntaxTextArea getEditArea() {
        return editArea;
    }

    public void release() {
        if (model != null) {
            model.getSettings().removeSettingsListener(settingsListener);
            model.removePropertyChangeListener(this);
        }

        model = null;
        editArea.getDocument().removeDocumentListener(groovyDocumentListener);
    }

    public void selectError(String message) {
        int ix = message == null ? -1 : message.indexOf("@ line ");
        if (ix >= 0) {
            try {
                int ix2 = message.indexOf(',', ix);
                int line = ix2 == -1 ? Integer.parseInt(message.substring(ix + 6).trim()) : Integer.parseInt(message
                        .substring(ix + 6, ix2).trim());
                int column = 0;
                if (ix2 != -1) {
                    ix = message.indexOf("column ", ix2);
                    if (ix >= 0) {
                        ix2 = message.indexOf('.', ix);
                        column = ix2 == -1 ? Integer.parseInt(message.substring(ix + 7).trim()) : Integer
                                .parseInt(message.substring(ix + 7, ix2).trim());
                    }
                }

                editArea.setCaretPosition(editArea.getLineStartOffset(line - 1) + column - 1);
            } catch (Exception ex) {
            }

            editArea.requestFocus();
        }
    }

    private final class GroovyDocumentListener extends DocumentListenerAdapter {
        public void update(Document document) {
            if (!updating) {
                GroovyEditor.this.model.setScript(editArea.getText());
            }
        }
    }

    private final class InternalSettingsListener implements SettingsListener {
        public void settingChanged(String name, String newValue, String oldValue) {
            if (name.equals(UISettings.EDITOR_FONT)) {
                Font newFont = Font.decode(newValue);
                setEditorFont(newFont);
                invalidate();
            }
        }

        @Override
        public void settingsReloaded() {
            // TODO Auto-generated method stub

        }
    }

    public void setEditorFont(Font newFont) {
        editArea.setFont(newFont);
    }

    public void addCaretListener(CaretListener listener) {
        editArea.addCaretListener(listener);
    }

    public int getCaretPosition() {
        return editArea.getCaretPosition();
    }

    public int getLineOfOffset(int offset) throws Exception {
        return editArea.getLineOfOffset(offset);
    }

    public int getLineStartOffset(int line) throws Exception {
        return editArea.getLineStartOffset(line);
    }

    public void removeCaretListener(CaretListener listener) {
        editArea.removeCaretListener(listener);
    }

    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals(SCRIPT_PROPERTY)) {
            updating = true;
            editArea.setText(String.valueOf(evt.getNewValue()));
            updating = false;
        }
    }

}
